package internal

import (
	"fmt"
	"sort"
	"strings"
)

type FieldMod int

const (
	Exported FieldMod = iota
	GetSet
	Get
	Set
)

type Fielder struct {
	Name    string
	Mod     FieldMod
	Type    string
	Imports map[string]ImportInfo
}

const _int64 = "int64"

// nolint: gochecknoglobals
var _toCode = map[string]func(targetType, sourceType, val string) string{
	"int":      toInteger,
	"int8":     toInteger,
	"int16":    toInteger,
	"int32":    toInteger,
	"int64":    toInteger,
	"uint":     toInteger,
	"uint8":    toInteger,
	"uint16":   toInteger,
	"uint32":   toInteger,
	"uint64":   toInteger,
	"float64":  toFloat,
	"float32":  toFloat,
	"ufloat64": toFloat,
	"ufloat32": toFloat,
	"string": func(_, sourceType, val string) string {
		if sourceType == "int" {
			return "strconv.Itoa(" + val + ")"
		}

		if strings.Contains(sourceType, "int") {
			return "strconv.Itoa(int(" + val + "))"
		}

		if sourceType == "float64" {
			return "strconv.FormatFloat(" + val + ", 'f', -1, 64)"
		}

		if sourceType == "float32" {
			return "strconv.FormatFloat(float64(" + val + "), 'f', -1, 32)"
		}

		if strings.Contains(sourceType, "float") {
			return "strconv.FormatFloat(float64(" + val + "), 'f', -1, 64)"
		}

		return "fmt.Sprintf(" + val + ")"
	},

	"bool": func(_, sourceType, val string) string {
		if strings.Contains(sourceType, "int") {
			return val + " > 0"
		}
		// TODO
		return "bool(" + val + ")"
	},
	"time.Time": func(_, sourceType, val string) string {
		if sourceType == _int64 {
			return fmt.Sprintf("conv.IfElse(%s>0, time.UnixMilli(%s), time.Time{})", val, val)
		}

		return fmt.Sprintf("conv.IfElse(%s>0, time.UnixMilli(int64(%s)), time.Time{})", val, val)
	},
}

func (p *Fielder) Val(sourceName, targetFieldType string) string {
	val := p.Name
	if p.Mod == Get || p.Mod == GetSet {
		val = "Get" + val + "()"
	}

	val = sourceName + "." + val

	if p.Type == targetFieldType {
		return val
	}

	if fun, has := _toCode[targetFieldType]; has {
		ret := fun(targetFieldType, p.Type, val)

		if strings.HasPrefix(ret, "conv.") || strings.Contains(ret, "(conv.") {
			p.Imports["gitee.com/xuender/bindgen/conv"] = ImportInfo{}
		}

		if strings.HasPrefix(ret, "strconv.") || strings.Contains(ret, "(strconv.") {
			p.Imports["strconv"] = ImportInfo{}
		}

		if strings.HasPrefix(ret, "fmt.") || strings.Contains(ret, "(ftm.") {
			p.Imports["fmt"] = ImportInfo{}
		}

		if strings.HasPrefix(ret, "time.") || strings.Contains(ret, " time.") || strings.Contains(ret, "(time.") {
			p.Imports["time"] = ImportInfo{}
		}

		return ret
	}

	return val
}

func toInteger(targetType, sourceType, val string) string {
	switch sourceType {
	case "time.Time":
		if targetType == _int64 {
			return val + ".UnixMilli()"
		}

		return targetType + "(" + val + ".UnixMilli())"
	case "bool":
		return "conv.IfElse[" + targetType + "](" + val + ", 1, 0)"
	case "string":
		if targetType == "int" {
			return "conv.Must(strconv.Atoi(" + val + "))"
		}

		return "conv.Must(strconv.Atoi(int(" + val + ")))"
	default:
		return targetType + "(" + val + ")"
	}
}

func toFloat(targetType, sourceType, val string) string {
	switch sourceType {
	case "time.Time":
		return targetType + "(" + val + ".UnixMilli())"
	case "bool":
		return "conv.IfElse(" + val + ", 1, 0)"
	case "string":
		if targetType == "float64" {
			return "conv.Must(strconv.ParseFloat(" + val + ", 64))"
		}

		return "float32(conv.Must(strconv.ParseFloat(" + val + ", 32)))"
	default:
		return targetType + "(" + val + ")"
	}
}

type Fielders []Fielder

func (p *Fielders) Add(fielder Fielder) {
	for idx, item := range *p {
		if item.Name == fielder.Name {
			(*p)[idx] = fielder

			return
		}
	}

	*p = append(*p, fielder)
}

func (p Fielders) Mixed(other Fielders) []string {
	ret := []string{}

	for _, item := range p {
		for _, otherItem := range other {
			if item.Name == otherItem.Name {
				ret = append(ret, item.Name)
			}
		}
	}

	sort.Strings(ret)

	return ret
}

func (p Fielders) Find(name string) Fielder {
	for _, item := range p {
		if item.Name == name {
			return item
		}
	}

	panic(name)
}
